// @ts-nocheck
import * as Util from './util.esm.js';
import CustomPrimitive from './custom-primitive.esm.js';

import { segmentDrawVertexShader, segmentDrawFragShader } from '../glsl/segmentDraw.shader.js';
import { fullscreenVertexShader } from '../glsl/fullscreen.vert.js';
import { trailDrawFragShader } from '../glsl/trailDraw.frag.js';
import { screenDrawFragShader } from '../glsl/screenDraw.frag.js';

export default class ParticlesRendering {
  constructor(context, data, userInput, viewerParameters, particlesComputing) {
    this.createRenderingTextures(context, data);
    this.createRenderingFramebuffers(context);
    this.createRenderingPrimitives(context, userInput, viewerParameters, particlesComputing);
  }

  createRenderingTextures(context, data) {
    const colorTextureOptions = {
      context: context,
      width: context.drawingBufferWidth,
      height: context.drawingBufferHeight,
      pixelFormat: Cesium.PixelFormat.RGBA,
      pixelDatatype: Cesium.PixelDatatype.UNSIGNED_BYTE
    };
    const depthTextureOptions = {
      context: context,
      width: context.drawingBufferWidth,
      height: context.drawingBufferHeight,
      pixelFormat: Cesium.PixelFormat.DEPTH_COMPONENT,
      pixelDatatype: Cesium.PixelDatatype.UNSIGNED_INT
    };

    this.textures = {
      segmentsColor: Util.createTexture(colorTextureOptions),
      segmentsDepth: Util.createTexture(depthTextureOptions),

      currentTrailsColor: Util.createTexture(colorTextureOptions),
      currentTrailsDepth: Util.createTexture(depthTextureOptions),

      nextTrailsColor: Util.createTexture(colorTextureOptions),
      nextTrailsDepth: Util.createTexture(depthTextureOptions)
    };
  }

  createRenderingFramebuffers(context) {
    this.framebuffers = {
      segments: Util.createFramebuffer(
        context,
        this.textures.segmentsColor,
        this.textures.segmentsDepth
      ),
      currentTrails: Util.createFramebuffer(
        context,
        this.textures.currentTrailsColor,
        this.textures.currentTrailsDepth
      ),
      nextTrails: Util.createFramebuffer(
        context,
        this.textures.nextTrailsColor,
        this.textures.nextTrailsDepth
      )
    };
  }

  createSegmentsGeometry(userInput) {
    const repeatVertex = 6;

    var st = [];
    for (var s = 0; s < userInput.particlesTextureSize; s++) {
      for (var t = 0; t < userInput.particlesTextureSize; t++) {
        for (var i = 0; i < repeatVertex; i++) {
          st.push(s / userInput.particlesTextureSize);
          st.push(t / userInput.particlesTextureSize);
        }
      }
    }
    st = new Float32Array(st);

    var normal = [];
    const pointToUse = [-1, 0, 1];
    const offsetSign = [-1, 1];
    for (var i = 0; i < userInput.maxParticles; i++) {
      for (var j = 0; j < pointToUse.length; j++) {
        for (var k = 0; k < offsetSign.length; k++) {
          normal.push(pointToUse[j]);
          normal.push(offsetSign[k]);
          normal.push(0);
        }
      }
    }
    normal = new Float32Array(normal);

    const indexSize = 12 * userInput.maxParticles;
    var vertexIndexes = new Uint32Array(indexSize);
    for (var i = 0, j = 0, vertex = 0; i < userInput.maxParticles; i++) {
      vertexIndexes[j++] = vertex + 0;
      vertexIndexes[j++] = vertex + 1;
      vertexIndexes[j++] = vertex + 2;

      vertexIndexes[j++] = vertex + 2;
      vertexIndexes[j++] = vertex + 1;
      vertexIndexes[j++] = vertex + 3;

      vertexIndexes[j++] = vertex + 2;
      vertexIndexes[j++] = vertex + 4;
      vertexIndexes[j++] = vertex + 3;

      vertexIndexes[j++] = vertex + 4;
      vertexIndexes[j++] = vertex + 3;
      vertexIndexes[j++] = vertex + 5;

      vertex += repeatVertex;
    }

    var geometry = new Cesium.Geometry({
      attributes: new Cesium.GeometryAttributes({
        st: new Cesium.GeometryAttribute({
          componentDatatype: Cesium.ComponentDatatype.FLOAT,
          componentsPerAttribute: 2,
          values: st
        }),
        normal: new Cesium.GeometryAttribute({
          componentDatatype: Cesium.ComponentDatatype.FLOAT,
          componentsPerAttribute: 3,
          values: normal
        })
      }),
      indices: vertexIndexes
    });

    return geometry;
  }

  createRenderingPrimitives(context, userInput, viewerParameters, particlesComputing) {
    const that = this;
    this.primitives = {
      segments: new CustomPrimitive({
        commandType: 'Draw',
        attributeLocations: {
          st: 0,
          normal: 1
        },
        geometry: this.createSegmentsGeometry(userInput),
        primitiveType: Cesium.PrimitiveType.TRIANGLES,
        uniformMap: {
          previousParticlesPosition: function () {
            return particlesComputing.particlesTextures.previousParticlesPosition;
          },
          currentParticlesPosition: function () {
            return particlesComputing.particlesTextures.currentParticlesPosition;
          },
          postProcessingPosition: function () {
            return particlesComputing.particlesTextures.postProcessingPosition;
          },
          aspect: function () {
            return context.drawingBufferWidth / context.drawingBufferHeight;
          },
          pixelSize: function () {
            return viewerParameters.pixelSize;
          },
          lineWidth: function () {
            return userInput.lineWidth;
          },
          particleHeight: function () {
            return userInput.particleHeight;
          }
        },
        vertexShaderSource: new Cesium.ShaderSource({
          sources: [segmentDrawVertexShader]
        }),
        fragmentShaderSource: new Cesium.ShaderSource({
          sources: [segmentDrawFragShader]
        }),
        rawRenderState: Util.createRawRenderState({
          // undefined value means let Cesium deal with it
          viewport: undefined,
          depthTest: {
            enabled: true
          },
          depthMask: true
        }),
        framebuffer: this.framebuffers.segments,
        autoClear: true
      }),

      trails: new CustomPrimitive({
        commandType: 'Draw',
        attributeLocations: {
          position: 0,
          st: 1
        },
        geometry: Util.getFullscreenQuad(),
        primitiveType: Cesium.PrimitiveType.TRIANGLES,
        uniformMap: {
          segmentsColorTexture: function () {
            return that.textures.segmentsColor;
          },
          segmentsDepthTexture: function () {
            return that.textures.segmentsDepth;
          },
          currentTrailsColor: function () {
            return that.framebuffers.currentTrails.getColorTexture(0);
          },
          trailsDepthTexture: function () {
            return that.framebuffers.currentTrails.depthTexture;
          },
          fadeOpacity: function () {
            return userInput.fadeOpacity;
          }
        },
        // prevent Cesium from writing depth because the depth here should be written manually
        vertexShaderSource: new Cesium.ShaderSource({
          defines: ['DISABLE_GL_POSITION_LOG_DEPTH'],
          sources: [fullscreenVertexShader]
        }),
        fragmentShaderSource: new Cesium.ShaderSource({
          defines: ['DISABLE_LOG_DEPTH_FRAGMENT_WRITE'],
          sources: [trailDrawFragShader]
        }),
        rawRenderState: Util.createRawRenderState({
          viewport: undefined,
          depthTest: {
            enabled: true,
            func: Cesium.DepthFunction.ALWAYS // always pass depth test for full control of depth information
          },
          depthMask: true
        }),
        framebuffer: this.framebuffers.nextTrails,
        autoClear: true,
        preExecute: function () {
          // swap framebuffers before binding
          var temp;
          temp = that.framebuffers.currentTrails;
          that.framebuffers.currentTrails = that.framebuffers.nextTrails;
          that.framebuffers.nextTrails = temp;

          // keep the framebuffers up to date
          that.primitives.trails.commandToExecute.framebuffer = that.framebuffers.nextTrails;
          that.primitives.trails.clearCommand.framebuffer = that.framebuffers.nextTrails;
        }
      }),

      screen: new CustomPrimitive({
        commandType: 'Draw',
        attributeLocations: {
          position: 0,
          st: 1
        },
        geometry: Util.getFullscreenQuad(),
        primitiveType: Cesium.PrimitiveType.TRIANGLES,
        uniformMap: {
          trailsColorTexture: function () {
            return that.framebuffers.nextTrails.getColorTexture(0);
          },
          trailsDepthTexture: function () {
            return that.framebuffers.nextTrails.depthTexture;
          }
        },
        // prevent Cesium from writing depth because the depth here should be written manually
        vertexShaderSource: new Cesium.ShaderSource({
          defines: ['DISABLE_GL_POSITION_LOG_DEPTH'],
          sources: [fullscreenVertexShader]
        }),
        fragmentShaderSource: new Cesium.ShaderSource({
          defines: ['DISABLE_LOG_DEPTH_FRAGMENT_WRITE'],
          sources: [screenDrawFragShader]
        }),
        rawRenderState: Util.createRawRenderState({
          viewport: undefined,
          depthTest: {
            enabled: false
          },
          depthMask: true,
          blending: {
            enabled: true
          }
        }),
        framebuffer: undefined // undefined value means let Cesium deal with it
      })
    };
  }
}
